%%
%% %CopyrightBegin%
%%
%% SPDX-License-Identifier: Apache-2.0
%%
%% Copyright Ericsson AB 2003-2025. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
-module(warnings_SUITE).

%%-define(STANDALONE, true).

-ifdef(STANDALONE).
-define(line, put(line, ?LINE), ).
-define(config(X,Y), foo).
-define(privdir, "warnings_SUITE_priv").
-define(t, test_server).
-else.
-include_lib("common_test/include/ct.hrl").
-define(datadir, proplists:get_value(data_dir, Conf)).
-define(privdir, proplists:get_value(priv_dir, Conf)).
-endif.

-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, 
	 init_per_group/2,end_per_group/2,
	 init_per_testcase/2,end_per_testcase/2]).

-export([pattern/1,pattern2/1,pattern3/1,pattern4/1,
	 guard/1,bad_arith/1,bool_cases/1,bad_apply/1,
         files/1,effect/1,bin_opt_info/1,bin_construction/1,
	 comprehensions/1,maps/1,maps_bin_opt_info/1,
         redundant_boolean_clauses/1,
	 underscore/1,no_warnings/1,
	 bit_syntax/1,inlining/1,tuple_calls/1,
         recv_opt_info/1,opportunistic_warnings/1,
         eep49/1,inline_list_funcs/1]).

init_per_testcase(_Case, Config) ->
    Config.

end_per_testcase(_Case, _Config) ->
    ok.

suite() ->
    [{ct_hooks,[ts_install_cth]},
     {timetrap,{minutes,2}}].

all() -> 
    [{group,p}].

groups() -> 
    [{p,test_lib:parallel(),
      [pattern,pattern2,pattern3,pattern4,guard,
       bad_arith,bool_cases,bad_apply,files,effect,
       bin_opt_info,bin_construction,comprehensions,maps,
       maps_bin_opt_info,
       redundant_boolean_clauses,
       underscore,no_warnings,bit_syntax,inlining,
       tuple_calls,recv_opt_info,opportunistic_warnings,
       eep49,inline_list_funcs]}].

init_per_suite(Config) ->
    test_lib:recompile(?MODULE),
    Config.

end_per_suite(_Config) ->
    ok.

init_per_group(_GroupName, Config) ->
    Config.

end_per_group(_GroupName, Config) ->
    Config.


pattern(Config) when is_list(Config) ->
    %% Test warnings generated by v3_core.
    Ts = [{pattern,
           <<"%% Just a comment here.
              f(a={glurf,2}=A) -> A.

              g(A) ->
                 case A of
                   a=[_|_] -> error;
                   Other -> true
                 end.

              foo(X) ->
                 a = {nisse,b} = X.
           ">>,
           [warn_unused_vars],
           {warnings,
            [{{2,15},v3_core,{nomatch,pattern}},
             {{6,20},v3_core,{nomatch,pattern}}
            ]}}],
    [] = run(Config, Ts),
    ok.

pattern2(Config) when is_list(Config) ->
    %% Test warnings generated by sys_core_fold.
    %% If we disable Core Erlang optimizations, we expect that
    %% beam_core_to_ssa should generate some of the warnings.
    Source = <<"f(A) -> ok;
                f(B) -> error.
                t(A, B, C) ->
                  case {A,B,C} of
                    {a,B} -> ok;
                    {_,B} -> ok
                  end.
                c(E) ->
                  case E of
                    _ -> ok;
                    _ -> ok
                  end.
           ">>,

    %% Test warnings from sys_core_fold.
    Ts = [{pattern2,
	   Source,
	   [nowarn_unused_vars],
           {warnings,[{{2,17},sys_core_fold,{nomatch,{shadow,1,{f,1}}}},
                      {{4,19},sys_core_fold,{nomatch,no_clause}},
                      {{5,21},sys_core_fold,{nomatch,clause_type}},
                      {{6,21},sys_core_fold,{nomatch,clause_type}},
                      {{11,21},sys_core_fold,{nomatch,{shadow,10}}}
                     ]}}],
    [] = run(Config, Ts),

    %% Disable Core Erlang optimizations. beam_core_to_ssa should produce
    %% a warning for the clause that didn't match.
    Ts2 = [{pattern2,
	    Source,
	    [nowarn_unused_vars,no_copt],
	    {warnings,
	     [{{2,17},beam_core_to_ssa,{nomatch,{shadow,1}}},
              {{11,21},beam_core_to_ssa,{nomatch,{shadow,10}}}
             ]}}],
    [] = run(Config, Ts2),
    ok.

pattern3(Config) when is_list(Config) ->
    %% Test warnings generated by the pattern matching compiler
    %% in beam_core_to_ssa.

    Ts = [{pattern3,
	   <<"
            f({A,_}) -> {ok,A};
            f([_|_]=B) -> {ok,B};
            f({urk,nisse}) -> urka_glurka.
            word(<<\"AND\">>) -> <<\"and\">>;
            word(<<\"AS\">>) -> <<\"as\">>;
            word(<<\"A\">>) -> <<\"a\">>;
            word(<<\"AS\">>) -> <<\"as\">>.
           ">>,
	   [nowarn_unused_vars],
	   {warnings,
            [{{4,13},beam_core_to_ssa,{nomatch,{shadow,2}}},
             {{8,13},beam_core_to_ssa,{nomatch,{shadow,6}}}]}}],
    [] = run(Config, Ts),

    ok.

pattern4(Config) when is_list(Config) ->
    %% Test warnings for clauses that cannot possibly match.

    Ts = [{pattern4,
	   <<"
             t() ->
               case true of 
                 false -> a;
                 true -> b
               end.

             fi() ->
               case true of 
                 false -> a;
                 false -> b
               end,
               case true of 
                 true -> a;
                 true -> b;
                 X -> X
               end,
               case boolean of 
                 true -> a;
                 false -> b
               end.
             int() ->
               case 42 of
                 [a|b] -> no;
                 <<1>> -> no;
                 <<X>> -> no;
                 17 -> no;
                 [] -> no;
                 a -> no;
                 {a,b,c} -> no
               end.
             tuple() ->
               case {x,y,z} of
                 \"xyz\" -> no;
                 [a|b] -> no;
                 <<1>> -> no;
                 <<X>> -> no;
                 17 -> no;
                 [] -> no;
                 a -> no;
                 {a,b,c} -> no;
                 {x,y} -> no
               end.
           ">>,
	   [nowarn_unused_vars],
	   {warnings,
            [{{9,16},sys_core_fold,{nomatch,no_clause}},
             {{11,18},sys_core_fold,{nomatch,shadow}},
             {{15,18},sys_core_fold,{nomatch,shadow}},
             {{18,16},sys_core_fold,{nomatch,no_clause}},
             {{23,16},sys_core_fold,{nomatch,no_clause}},
             {{33,16},sys_core_fold,{nomatch,no_clause}}
	    ]}}],
    [] = run(Config, Ts),

    ok.

guard(Config) when is_list(Config) ->
    %% Test warnings for false guards.

    Ts = [{guard,
	   <<"
              t(A, B) when element(x, dum) -> ok.

              tt(A, B) when 1 == 2 -> ok.

              ttt() when element(x, dum) -> ok.

              t4(T, F) when element({F}, T) -> ok.
              t5(T, F) when element([F], T) -> ok.
              t6(Pos, F) when element(Pos, [F]) -> ok.
              t7(Pos) when element(Pos, []) -> ok.
           ">>,
	   [nowarn_unused_vars],
	   {warnings,
            [{{2,28},sys_core_fold,
              {failed,{eval_failure,
                       {erlang,element,2},
                       badarg}}},
             {{4,15},sys_core_fold,{nomatch,guard}},
             {{4,15},sys_core_fold,{nomatch,no_clause}},
             {{6,26},sys_core_fold,
              {failed,
               {eval_failure,
                {erlang,element,2},
                badarg}}}
	    ]}}],
    [] = run(Config, Ts),

    ok.

bad_arith(Config) when is_list(Config) ->
    Ts = [{bad_arith,
           <<"f() ->
                if
                  a + 3 > 3 -> ok;
                 true -> error
              end.

              g(A) ->
                if
                  is_integer(A), a + 3 > 3 -> ok;
                  a + 3 > 42, is_integer(A) -> ok;
                 true -> error
              end.

              h(A) ->
                a + 3 + A.
           ">>,
	   [],
	   {warnings,
            [{{3,21},sys_core_fold,
              {failed,{eval_failure,
                       {erlang,'+',2},
                       badarith}}},
             {{9,36},sys_core_fold,
              {failed,{eval_failure,
                       {erlang,'+',2},
                       badarith}}},
             {{10,21},sys_core_fold,
              {failed,{eval_failure,
                       {erlang,'+',2},
                       badarith}}},
             {{15,19},sys_core_fold,
              {failed,{eval_failure,
                       {erlang,'+',2},
                       badarith}}}
	    ] }}],
    [] = run(Config, Ts),
    ok.

bool_cases(Config) when is_list(Config) ->
    Ts = [{bool_cases,
	   <<"
            f(A, B) ->
               case A > B of
                 true -> true;
                 false -> false;
                 Other -> {error,not_bool}
               end.

            g(A, B) ->
               case A =/= B of
                 false -> false;
                 true -> true;
                 Other -> {error,not_bool}
               end.

            h(Bool) ->
               case not Bool of
                 'maybe' -> strange;
                 false -> ok;
                 true -> error
               end.
           ">>,
           [nowarn_unused_vars],
           {warnings,
            [{{6,18},sys_core_fold,{nomatch,shadow}},
             {{13,18},sys_core_fold,{nomatch,shadow}},
             {{18,18},sys_core_fold,{nomatch,clause_type}} ]} }],
    [] = run(Config, Ts),
    ok.

bad_apply(Config) when is_list(Config) ->
    Ts = [{bad_apply,
	   <<"
             t(1) -> 42:42();
             t(2) -> erlang:42();
             t(3) -> 42:start();
             t(4) -> []:start();
             t(5) -> erlang:[]();
             t(6) -> [a,b,c]().
           ">>,
           [],
           {warnings,
            [{{2,22},beam_core_to_ssa,{failed,bad_call}},
             {{3,22},beam_core_to_ssa,{failed,bad_call}},
             {{4,22},beam_core_to_ssa,{failed,bad_call}},
             {{5,22},beam_core_to_ssa,{failed,bad_call}},
             {{6,22},beam_core_to_ssa,{failed,bad_call}},
             {{7,22},sys_core_fold,{failed,bad_call}}
            ]}}],
    [] = run(Config, Ts),

    %% Also verify that the generated code generates the correct error.
    try erlang:42() of
	      _ -> ct:fail(should_fail)
	  catch
	      error:badarg -> ok
	  end,
    ok.

files(Config) when is_list(Config) ->
    Ts = [{files_1,
	   <<"
              -file(\"file1\", 14).

              t1() ->
                  1/0.

              -file(\"file2\", 7).

              t2() ->
                  1/0.
           ">>,
           [],
           {warnings,
            [{"file1",[{{17,20},sys_core_fold,
                        {failed,{eval_failure,
                                 {erlang,'/',2},
                                 badarith}}}]},
             {"file2",[{{10,20},sys_core_fold,
                        {failed,{eval_failure,
                                 {erlang,'/',2},
                                 badarith}}}]}]}}],

    [] = run(Config, Ts),
    ok.

%% Test warnings for term construction and BIF calls in effect context.
effect(Config) when is_list(Config) ->
    Ts = [{no_warnings,
           %% No warnings should be generated in the following functions.
           <<"
             m1(X, Sz) ->
                if
                  Sz =:= 0 -> X = 0;
                  true -> ok
                end,
                ok.

             m2(X, Sz) ->
                if
                  Sz =:= 0 -> X = {a,Sz};
                  true -> ok
                end,
                ok.

             m3(X, Sz) ->
                if
                  Sz =:= 0 -> X = [a,Sz];
                  true -> ok
                end,
                ok.

             m4(X, Sz, Var) ->
                if
                  Sz =:= 0 -> X = Var;
                  true -> ok
                end,
                ok.

             m5(X, Sz) ->
                if
                   Sz =:= 0 -> X = {a,b,c};
                   true -> ok
                end,
                ok.

             m6(X, Sz) ->
                if
                  Sz =:= 0 -> X = {a,Sz,[1,2,3]};
                  true -> ok
                end,
                ok.

             m7(X, Sz) ->
                if
                  Sz =:= 0 -> X = {a,Sz,[1,2,3],abs(Sz)};
                  true -> ok
                end,
                ok.

             m8(A, B) ->
                case {A,B} of
                  V -> V
                end,
                ok.

             m9(Bs) ->
                [{B,ok} = {B,foo:bar(B)} || B <- Bs],
                ok.

             m10(ConfigTableSize) ->
               case ConfigTableSize of
                 apa ->
                   CurrentConfig = {id(camel_phase3),id(sms)},
                   case CurrentConfig of
                     {apa, bepa} -> ok;
                     _ -> ok
                   end
               end,
               ok.

             id(I) -> I.
             ">>,
           [],[]},

          {basic,
           <<"
             t(X) ->
               case X of
                warn_lc ->
                    [is_integer(Z) || Z <- [1,2,3]];
                warn_lc_2 ->
                    [{error,Z} || Z <- [1,2,3]];
                warn_lc_3 ->
                    [{error,abs(Z)} || Z <- [1,2,3]];
                no_warn_lc ->
                    [put(last_integer, Z) || Z <- [1,2,3]]; %no warning
                unused_tuple_literal ->
                    {a,b,c};
                unused_list_literal ->
                    [1,2,3,4];
                unused_integer ->
                    42;
                unused_arith ->
                    X*X
               end,
               ok.
             ">>,
           [],
           {warnings,[{{5,22},sys_core_fold,{ignored,{no_effect,{erlang,is_integer,1}}}},
                      {{7,22},sys_core_fold,{ignored,useless_building}},
                      {{9,22},sys_core_fold,{ignored,useless_building}},
                      {{9,29},sys_core_fold,{ignored,{result,{erlang,abs,1}}}},
                      {{13,21},sys_core_fold,{ignored,useless_building}},
                      {{15,21},sys_core_fold,{ignored,useless_building}},
                      {{17,21},sys_core_fold,{ignored,useless_building}},
                      {{19,22},sys_core_fold,{ignored,{result, {erlang,'*',2}}}}
                     ]}},

          {nested,
            <<"
             t(X) ->
               case X of
                nested ->
                    [{ok,node(),module:foo(),self(),[time(),date()],time()},
                     is_integer(X)];
                unused_bit_syntax ->
                    <<X:8>>;
                unused_fun ->
                    fun() -> {ok,X} end;
                unused_named_fun ->
                    fun F(0) -> 1;
                        F(N) -> N*F(N-1)
                    end;
                unused_atom ->
                    ignore;                             %no warning
                unused_nil ->
                    [];                                 %no warning
                comp_op ->
                    X =:= 2;
                cookie ->
                    erlang:get_cookie();
                result_ignore ->
                    _ = list_to_integer(X);
                warn_lc_4 ->
                    %% No warning because of assignment to _.
                    [_ = abs(Z) || Z <- [1,2,3]]
               end,
               ok.
             ">>,
           [],
           {warnings,[{{5,21},sys_core_fold,{ignored,useless_building}},
                      {{5,26},sys_core_fold,{ignored,{no_effect,{erlang,node,0}}}},
                      {{5,46},sys_core_fold,{ignored,{no_effect,{erlang,self,0}}}},
                      {{5,54},sys_core_fold,{ignored,{no_effect,{erlang,time,0}}}},
                      {{5,61},sys_core_fold,{ignored,{no_effect,{erlang,date,0}}}},
                      {{5,69},sys_core_fold,{ignored,{no_effect,{erlang,time,0}}}},
                      {{6,22},sys_core_fold,{ignored,{no_effect,{erlang,is_integer,1}}}},
                      {{8,21},sys_core_fold,{ignored,useless_building}},
                      {{10,21},sys_core_fold,{ignored,useless_building}},
                      {{12,21},sys_core_fold,{ignored,useless_building}},
                      {{20,23},sys_core_fold,{ignored,{no_effect,{erlang,'=:=',2}}}},
                      {{22,21},sys_core_fold,{ignored,{no_effect,{erlang,get_cookie,0}}}}
                      ]}},

          {seq,
           <<"
             t(T) ->
               [ {a,b,T} ],  [ {x,y,T} ],
               ok.
             ">>,
           [],
           {warnings,[{{3,16},sys_core_fold,{ignored,useless_building}},
                      {{3,30},sys_core_fold,{ignored,useless_building}}]}},

          {propagated_literal,
           <<"
            foo(X) ->
                Y = [$.],
                %% There must not be a warning for constructing a term that
                %% is never used.
                fun() -> X = Y ++ [$.] end(),
                ok.
             ">>,
           [],
           []}
         ],
    [] = run(Config, Ts),
    ok.

bin_opt_info(Config) when is_list(Config) ->
    Code = <<"
             t1(Bin) ->
               case Bin of
                 _ when byte_size(Bin) > 20 -> erlang:error(too_long);
                 <<_,T/binary>> -> t1(T);
                 <<>> -> ok
             end.

             %% We use a tail in a BIF instruction, remote call, function
             %% return, and an optimizable tail call for better coverage.
             t2(<<A,B,T/bytes>>) ->
                 if
                     A > B -> t2(T);
                     A =< B -> T
                 end;
             t2(<<_,T/bytes>>) when byte_size(T) < 4 ->
                 foo;
             t2(<<_,T/bytes>>) ->
                 split_binary(T, 4).
           ">>,

    Ws = (catch run_test(Config, Code, [bin_opt_info])),

    %% This is an inexact match since the pass reports exact instructions as
    %% part of the warnings, which may include annotations that vary from run
    %% to run.
    {warnings,
     [{5,beam_ssa_bsm,{unsuitable_call,
                       {{b_local,{b_literal,t1},1},
                        {used_before_match,
                         {b_set,_,_,{bif,byte_size},[_]}}}}},
      {5,beam_ssa_bsm,{binary_created,_,_}},
      {11,beam_ssa_bsm,{binary_created,_,_}}, %% A =< B -> T
      {13,beam_ssa_bsm,context_reused},       %% A > B -> t2(T);
      {16,beam_ssa_bsm,context_reused}, %% when byte_size(T) < 4 ->
      {19,beam_ssa_bsm,{remote_call,
                        {b_remote,
                         {b_literal,erlang},
                         {b_literal,split_binary},2}}},
      {19,beam_ssa_bsm,{binary_created,_,_}}  %% split_binary(T, 4)
     ]} = Ws,

    %% For coverage: don't give the bin_opt_info option.
    [] = (catch run_test(Config, Code, [])),

    %% Now try with abstract code and no location.
    %%
    %% t1(Bin) ->
    %%   case Bin of
    %%     _ when byte_size(Bin) > 20 -> erlang:error(too_long);
    %%     <<_,T/binary>> -> t1(T);
    %%     <<>> -> ok
    %% end.
    Forms = [{attribute,0,module,nolocation_binary},
             {attribute,0,export,[{t1,1}]},
             {function,0,t1,1,
                [{clause,0,[{var,0,'Bin'}],[],
                     [{'case',0,{var,0,'Bin'},
                          [{clause,0,
                               [{var,0,'_'}],
                               [[{op,0,'>',
                                     {call,0,{atom,0,byte_size},[{var,0,'Bin'}]},
                                     {integer,0,20}}]],
                               [{call,0,
                                    {remote,0,{atom,0,erlang},{atom,0,error}},
                                    [{atom,0,too_long}]}]},
                           {clause,0,
                               [{bin,0,
                                    [{bin_element,0,{var,0,'_'},default,default},
                                     {bin_element,0,{var,0,'T'},default,[binary]}]}],
                               [],
                               [{call,0,{atom,0,t1},[{var,0,'T'}]}]},
                           {clause,0,[{bin,0,[]}],[],[{atom,0,ok}]}]}]}]}],
    Wsf = (catch run_forms(Forms, [bin_opt_info])),

    {warnings,
     [{none,beam_ssa_bsm,{unsuitable_call,
                       {{b_local,{b_literal,t1},1},
                        {used_before_match,
                         {b_set,_,_,{bif,byte_size},[_]}}}}},
      {none,beam_ssa_bsm,{binary_created,_,_}}
     ]} = Wsf,

    ok.

bin_construction(Config) when is_list(Config) ->
    Ts = [{bin_construction,
	   <<"
             t() ->
                 Bin = <<1,2,3>>,
                 <<Bin:4/binary>>.

             x() ->
                 Bin = <<1,2,3,7:4>>,
                 <<Bin/binary>>.

             y() -> <<0.5>>.
             z() -> <<99999999999999/utf8>>.
             w() -> <<0.5:1/float>>.

             a() ->
               Size = bad_size,
               <<1:Size>>.
           ">>,
           [],
           {warnings,[{{4,18},sys_core_fold,{failed,embedded_binary_size}},
                      {{8,18},sys_core_fold,{failed,{embedded_unit,8,28}}},
                      {{10,21},v3_core,{failed,bad_binary}},
                      {{11,21},sys_core_fold,{failed,bad_unicode}},
                      {{12,21},sys_core_fold,{failed,bad_float_size}},
                      {{16,18},beam_core_to_ssa,{failed,bad_segment_size}}
                     ]}}],
    [] = run(Config, Ts),

    ok.

comprehensions(Config) when is_list(Config) ->
    Ts = [{tautologic_guards,
           <<"
             f() -> [ true || true ].
             g() -> << <<1>> || true >>.
           ">>,
           [], []}],
    run(Config, Ts),
    ok.

maps(Config) when is_list(Config) ->
    Ts = [{bad_map,
           <<"
             t() ->
                 case maybe_map of
                     #{} -> ok;
                     not_map -> error
                 end.
             x() ->
                 case true of
                     #{}  -> error;
                     true -> ok
                 end.
           ">>,
           [],
           {warnings,[{{3,18},sys_core_fold,{nomatch,no_clause}},
                      {{9,22},sys_core_fold,{nomatch,clause_type}}]}},
	   {bad_map_src1,
           <<"
             t() ->
                 M = {a,[]},
                 {'EXIT',{badarg,_}} = (catch(M#{ a => 1 })),
                 ok.
           ">>,
           [],
           {warnings,[{{4,48},sys_core_fold,{failed,bad_map_update}}]}},
	   {bad_map_src2,
           <<"
             t() ->
		 M = id({a,[]}),
		 {'EXIT',{badarg,_}} = (catch(M#{ a => 1})),
		 ok.
	     id(I) -> I.
           ">>,
	   [inline],
	    []},
	   {bad_map_src3,
           <<"
             t() ->
                 {'EXIT',{badarg,_}} = (catch <<>>#{ a := 1}),
                 ok.
           ">>,
           [],
           {warnings,[{{3,51},sys_core_fold,{failed,bad_map_update}}]}},
           {ok_map_literal_key,
           <<"
             t() ->
		 V = id(1),
		 M = id(#{ <<$h,$i>> => V }),
		 V = case M of
		    #{ <<0:257>> := Val } -> Val;
		    #{ <<$h,$i>> := Val } -> Val
		 end,
		 ok.
	     id(I) -> I.
           ">>,
           [],
	   []},
           {repeated_keys1,
           <<"
             foo1() ->
                 #{a=>1,
                   b=> 2,
                   a=>3}.
             
             bar1(M) ->
                 M#{a=>1, b=> 2, a:=3}.
             
             baz1(M) ->
                 M#{a=>1, b=> 2, a:=3}.
             
             foo2() ->
                 #{\"a\"=>1, \"b\"=> 2, \"a\"=>3}.
             
             bar2(M) ->
                 M#{\"a\"=>1, \"b\"=> 2, \"a\":=3}.
             
             baz2(M) ->
                 M#{\"a\"=>1, \"b\"=> 2, \"a\":=3}.
             
             foo3() ->
                 #{\"a\"=>1,
                   \"b\"=> 2,
                   \"a\"=>3}.
             
             bar3(M) ->
                 M#{\"a\"=>1, \"b\"=> 2, \"a\":=3}.
             
             baz3(M) ->
                 M#{<<\"a\">>=>1, <<\"b\">>=> 2, <<\"a\">>:=3}.
           ">>,
           [],
           {warnings,[{{3,20},v3_core,{map_key_repeated,a}},
                      {{8,21},v3_core,{map_key_repeated,a}},
                      {{11,21},v3_core,{map_key_repeated,a}},
                      {{14,20},v3_core,{map_key_repeated,"a"}},
                      {{17,21},v3_core,{map_key_repeated,"a"}},
                      {{20,21},v3_core,{map_key_repeated,"a"}},
                      {{23,20},v3_core,{map_key_repeated,"a"}},
                      {{28,21},v3_core,{map_key_repeated,"a"}},
                      {{31,21},v3_core,{map_key_repeated,<<"a">>}}
                     ]}},
           {repeated_keys2,
           <<"
             foo4(K) ->
                 #{\"a\"=>1, K => 1, \"b\"=> 2, \"a\"=>3, K=>2}.
             
             bar4(M,K) ->
                 M#{a=>1, K =>1, b=> 2, a:=3, K=>2}.
             
             baz4(M,K) ->
                 M#{<<\"a\">>=>1,
                     K => 1, <<\"b\">>=> 2,
                     <<\"a\">>:=3, K=>2}.
             
             foo5(K) ->
                 #{{\"a\",1}=>1, K => 1, \"b\"=> 2, {\"a\",1}=>3, K=>2}.
             
             bar5(M,K) ->
                 M#{{\"a\",<<\"b\">>}=>1, K =>1,
                    \"b\"=> 2, {\"a\",<<\"b\">>}:=3, K=>2}.
             
             baz5(M,K) ->
                 M#{{<<\"a\">>,1}=>1, K => 1,
                    <<\"b\">>=> 2, {<<\"a\">>,1}:=3,K=>2}.
             
             foo6(K) ->
                 #{#{\"a\"=>1}=>1, K => 1, \"b\"=> 2, #{\"a\"=>1}=>3, K=>2}.
             
             bar6(M,K) ->
                 M#{#{\"a\"=><<\"b\">>}=>1, K =>1,
                    \"b\"=> 2, #{\"a\"=><<\"b\">>}:=3, K=>2}.
             
             baz6(M,K) ->
                 M#{#{<<\"a\">>=>1}=>1,
                    K => 1,
                    <<\"b\">>=> 2,
                    #{<<\"a\">>=>1}:=3,K=>2}.
             
             foo7(K) ->
                 M1 = #{#{\"a\"=>1}=>1, K => 1, \"b\"=> 2},
                 M1#{#{\"a\"=>1}=>3, K=>2}.
             
             bar7(M,K) ->
                 M1 = M#{#{\"a\"=><<\"b\">>}=>1, K =>1, \"b\"=> 2},
                 M1#{#{\"a\"=><<\"b\">>}:=3, K=>2}.
             
             baz7(M,K) ->
                 M1 = M#{#{<<\"a\">>=>1}=>1,
                    K => 1,
                    <<\"b\">>=> 2},
                 M1#{#{<<\"a\">>=>1}:=3,K=>2}.
          ">>,
           [],
           {warnings,[{{3,20},v3_core,{map_key_repeated,"a"}},
                      {{6,21},v3_core,{map_key_repeated,a}},
                      {{9,21},v3_core,{map_key_repeated,<<"a">>}},
                      {{14,20},v3_core,{map_key_repeated,{"a",1}}},
                      {{17,21},v3_core,{map_key_repeated,{"a",<<"b">>}}},
                      {{21,21},v3_core,{map_key_repeated,{<<"a">>,1}}},
                      {{25,20},v3_core,{map_key_repeated,#{"a" => 1}}},
                      {{28,21},v3_core,{map_key_repeated,#{"a" => <<"b">>}}},
                      {{32,21},v3_core,{map_key_repeated,#{<<"a">> => 1}}}
                     ]}},
          {map_nomatch,
           ~"""
            match_map_1(#{}) ->
                a;
            match_map_1(#{first := First}) ->
                {b,First};
            match_map_1(#{first := First, second := Second}) ->
                {c,First,Second}.

            match_map_1(#{}, A) ->
                {a,A};
            match_map_1(#{first := First}, A) ->
                {b,A,First};
            match_map_1(#{first := First, second := Second}, A) ->
                {c,A,First,Second}.

            match_map_2(#{first := First}) ->
                {b,First};
            match_map_2(#{first := First, second := Second}) ->
                {c,First,Second}.

            match_map_2(#{first := First}, A, B) ->
                {b,A,B,First};
            match_map_2(#{first := First, second := Second}, A, B) ->
                {c,A,B,First,Second}.

            match_map_3([#{} | _]) ->
                a;
            match_map_3([#{first := First} | _]) ->
                {b,First};
            match_map_3([#{first := First, second := Second} | _]) ->
                {c,First,Second}.

            match_map_4([#{first := First} | _]) ->
                {b,First};
            match_map_4([#{first := First, second := Second} | _]) ->
                {c,First,Second}.
            """,
           [],
           {warnings,[{{3,1},beam_core_to_ssa,{nomatch,{shadow,1}}},
                      {{10,1},beam_core_to_ssa,{nomatch,{shadow,8}}},
                      {{17,1},beam_core_to_ssa,{nomatch,{shadow,15}}},
                      {{22,1},beam_core_to_ssa,{nomatch,{shadow,20}}},
                      {{27,1},beam_core_to_ssa,{nomatch,{shadow,25}}},
                      {{34,1},beam_core_to_ssa,{nomatch,{shadow,32}}}]}},
          {map_nowarn,
           %% There will be no warnings for shadowing for the
           %% following functions, either because the first clause
           %% actually can be executed or because the compiler's
           %% checks are not sufficiently thorough.
           ~"""
            %% The compiler does not detect this shadowing.
            match_map_nowarn_1([#{}]) -> no;
            match_map_nowarn_1([#{a := A}]) -> {a,A}.

            %% The guard in the first clause can fail.
            match_map_nowarn_2(#{}, X) when is_integer(X) -> {a,X};
            match_map_nowarn_2(#{b := B}, X) -> {b,X,B}.

            %% The first clause will fail to match if the second
            %% argument is not `x`.
            match_map_nowarn_3(#{}, x) -> a;
            match_map_nowarn_3(#{b := B}, y) -> {b,B}.

            %% The compiler does not detect this shadowing.
            match_map_nowarn_4(#{}, x) -> a;
            match_map_nowarn_4(#{b := B}, x) -> {b,B}.
            """,
           [],
           []}
         ],
    run(Config, Ts),
    ok.

maps_bin_opt_info(Config) when is_list(Config) ->
    Ts = [{map_bsm,
           <<"
             t1(<<0:8,7:8,T/binary>>,#{val := I}=M) ->
                 t1(T, M#{val := I+1});
             t1(<<_:8>>,M) ->
                 M.
           ">>,
           [bin_opt_info],
           {warnings,[{3,beam_ssa_bsm,context_reused}]}}],
    [] = run(Config, Ts),
    ok.

redundant_boolean_clauses(Config) when is_list(Config) ->
    Ts = [{redundant_boolean_clauses,
           <<"
             t(X) ->
                 case X == 0 of
                     false -> no;
                     false -> no;
                     true -> yes
                 end.
           ">>,
           [],
           {warnings,[{{5,22},sys_core_fold,{nomatch,shadow}}]}}],
    run(Config, Ts),
    ok.

underscore(Config) when is_list(Config) ->
    %% The code template.
    S0 = <<"
            f(A) ->
              _VAR1 = <<A>>,
              _VAR2 = {ok,A},
              _VAR3 = [A],
              ok.
            g(A) ->
              _VAR1 = A/0,
              _VAR2 = date(),
              ok.
            h() ->
               _VAR1 = fun() -> ok end,
              ok.
            i(A) ->
               _VAR1 = #{A=>42},
              ok.
	 ">>,

    %% Define all possible warnings.
    Warnings = [{{3,23},sys_core_fold,{ignored,useless_building}},
                {{4,23},sys_core_fold,{ignored,useless_building}},
                {{5,23},sys_core_fold,{ignored,useless_building}},
                {{8,24},sys_core_fold,{ignored,{result,{erlang,'/',2}}}},
                {{9,23},sys_core_fold,{ignored,{no_effect,{erlang,date,0}}}},
                {{12,24},sys_core_fold,{ignored,useless_building}},
                {{15,24},sys_core_fold,{ignored,useless_building}}],


    %% Compile the unmodified template. Assigning to variable that
    %% begins with '_' should suppress all warnings.
    Ts0 = [{underscore0,S0,[],[]}],
    [] = run(Config, Ts0),

    %% Replace all "_VAR<digit>" variables with a plain underscore.
    %% There should still be no warnings.
    S1 = re:replace(S0, "_VAR\\d+", "_", [global]),
    io:format("~s\n", [S1]),
    Ts1 = [{underscore1,S1,[],[]}],
    [] = run(Config, Ts1),

    %% Make sure that we get warnings if we remove "_VAR<digit> = ".
    S2 = re:replace(S0, "_VAR\\d = ", "        ", [global]),
    io:format("~s\n", [S2]),
    Ts2 = [{underscore2,S2,[],{warnings,Warnings}}],
    [] = run(Config, Ts2),

    %% We should also get warnings if we assign to a variables that don't
    %% begin with underscore (as well as warnings for unused variables from
    %% erl_lint).
    S3 = re:replace(S0, "_(?=VAR\\d+)", " ", [global]),
    io:format("~s\n", [S3]),
    Ts3 = [{underscore2,S3,[],{warnings,Warnings}}],
    [] = run(Config, Ts3),

    ok.

no_warnings(Config) when is_list(Config) ->
    Ts = [{no_warnings,
           <<"-record(r, {s=ordsets:new(),a,b}).

              a() ->
                R = #r{},			%No warning expected.
                {R#r.a,R#r.b}.

              b(X) ->
                T = true,
                Var = [X],			%No warning expected.
                case T of
	          false -> Var;
                  true -> []
                end.

              c() ->
                R0 = {r,\"abc\",undefined,os:timestamp()}, %No warning.
                case R0 of
	          {r,V1,_V2,V3} -> {r,V1,\"def\",V3}
                end.

              d(In0, Bool) ->
                {In1,Int} = case id(Bool) of
                              false -> {In0,0}
                            end,
                [In1,Int].

              id(I) -> I.
           ">>,
           [],
           []}],
    run(Config, Ts),
    ok.

bit_syntax(Config) ->
    Ts = [{?FUNCTION_NAME,
           <<"
              a(<<-1>>) -> ok;
              a(<<1023>>) -> ok;
              a(<<777/signed>>) -> ok;
              a(<<a/binary>>) -> ok;
              a(<<a/integer>>) -> ok;
              a(<<a/float>>) -> ok;
              a(<<a/utf8>>) -> ok;
              a(<<a/utf16>>) -> ok;
              a(<<a/utf32>>) -> ok;
              a(<<a/utf32>>) -> ok.
              b(Bin) -> Sz = bad, <<42:Sz>> = Bin.
              c(Sz, Bin) ->
                case Bin of
                  <<-42:Sz/unsigned>> -> ok;
                  <<42:Sz/float>> -> ok;
                  <<42:Sz/binary>> -> ok
                end.
              d(<<16#110000/utf8>>) -> error;
              d(_) -> ok.
              e(<<X:1/big-signed-unit:64>>) -> {int, X};
              e(<<X:1/big-unsigned-float-unit:64>>) -> {float, X}.
              f(<<X:1/big-unsigned-float-unit:64>>) -> {float, X};
              f(<<X:1/big-signed-unit:64>>) -> {int, X}.
              g(<<X:4/signed-binary>>) -> X;
              g(<<X:1/unsigned-binary-unit:32>>) -> X.
             ">>,
	   [],
           {warnings,[{{2,15},sys_core_fold,{nomatch,no_clause}},
                      {{2,19},sys_core_fold,{nomatch,{bit_syntax_unsigned,-1}}},
                      {{3,19},sys_core_fold,{nomatch,{bit_syntax_truncated,
                                             unsigned,1023,8}}},
                      {{4,19},sys_core_fold,{nomatch,{bit_syntax_truncated,
                                             signed,777,8}}},
                      {{5,19},sys_core_fold,{nomatch,{bit_syntax_type,a,binary}}},
                      {{6,19},sys_core_fold,{nomatch,{bit_syntax_type,a,integer}}},
                      {{7,19},sys_core_fold,{nomatch,{bit_syntax_type,a,float}}},
                      {{8,19},sys_core_fold,{nomatch,{bit_syntax_type,a,utf8}}},
                      {{9,19},sys_core_fold,{nomatch,{bit_syntax_type,a,utf16}}},
                      {{10,19},sys_core_fold,{nomatch,{bit_syntax_type,a,utf32}}},
                      {{11,19},sys_core_fold,{nomatch,{bit_syntax_type,a,utf32}}},
                      {{12,35},sys_core_fold,{nomatch,no_clause}},
                      {{12,37},sys_core_fold,{nomatch,{bit_syntax_size,bad}}},
                      {{15,21},sys_core_fold,{nomatch,{bit_syntax_unsigned,-42}}},
                      {{17,21},sys_core_fold,{nomatch,{bit_syntax_type,42,binary}}},
                      {{19,19},sys_core_fold,{nomatch,{bit_syntax_unicode,1114112}}},
                      {{22,15},beam_core_to_ssa,{nomatch,{shadow,21}}},
                      {{26,15},beam_core_to_ssa,{nomatch,{shadow,25}}}
                     ]}
          }],
    run(Config, Ts),
    ok.

inlining(Config) ->
    %% Make sure that no spurious warnings are generated
    %% when inlining.
    Ts = [{inlining_1,
           <<"-compile(inline).
              compute1(X) -> add(X, 0).
              add(1, 0) -> 1;
              add(1, Y) -> 1 + Y;
              add(X, Y) -> X + Y.
           ">>,
           [],
           []},
	  {inlining_2,
           <<"-compile({inline,[add/2]}).
              compute1(X) -> add(X, 0).
              add(1, 0) -> 1;
              add(1, Y) -> 1 + Y;
              add(X, Y) -> X + Y.
           ">>,
           [],
           []}
	 ],
    run(Config, Ts),
    ok.

tuple_calls(Config) ->
    %% Make sure that no spurious warnings are generated.
    Ts = [{inlining_1,
           <<"-compile(tuple_calls).
              dispatch(X) ->
                (list_to_atom(\"prefix_\" ++
                atom_to_list(suffix))):doit(X).
           ">>,
           [],
           []}
	 ],
    run(Config, Ts),
    ok.

recv_opt_info(Config) when is_list(Config) ->
    Code = <<"
                simple_receive() ->
                    receive
                        Message -> handle:msg(Message)
                    end.

                selective_receive(Tag, Message) ->
                    receive
                        {Tag, Message} -> handle:msg(Message)
                    end.

                cross_function_receive() ->
                    cross_function_receive_1(make_ref()).

                cross_function_receive_1(Tag) ->
                    receive
                        {Tag, Message} -> handle:msg(Message)
                    end.

                optimized_receive(Process, Request) ->
                    MRef = monitor(process, Process),
                    Process ! {self(), MRef, Request},
                    receive
                        {MRef, Reply} ->
                            erlang:demonitor(MRef, [flush]),
                            handle:reply(Reply);
                        {'DOWN', MRef, _, _, Reason} ->
                            handle:error(Reason)
                    end.
           ">>,

    Ws = (catch run_test(Config, Code, [recv_opt_info])),

    %% This is an inexact match since the pass reports exact instructions as
    %% part of the warnings, which may include annotations that vary from run
    %% to run.
    {warnings,
        [%% simple_receive/0
         {3,beam_ssa_recv,matches_any_message},
         %% selective_receive/2
         {8,beam_ssa_recv,unoptimized_selective_receive},
         {13,beam_ssa_recv,reserved_receive_marker},
         %% cross_function_receive/0
         {13,beam_ssa_recv,{passed_marker,_}},
         %% cross_function_receive_1/1
         {16,beam_ssa_recv,{used_receive_marker,{parameter,1}}},
         %% optimized_receive/2
         {21,beam_ssa_recv,reserved_receive_marker},
         {23,beam_ssa_recv,{used_receive_marker,_}}]} = Ws,

    %% For coverage: don't give the recv_opt_info option.
    [] = (catch run_test(Config, Code, [])),

    %% Now try with abstract code and no location.
    %%
    %% simple_receive() ->
    %%     receive
    %%         Message -> handle:msg(Message)
    %%     end.
    Forms = [{attribute,0,module,nolocation_recv},
             {attribute,0,export,[{t1,0}]},
             {function,0,t1,0,
                 [{clause,0,[],[],
                      [{'receive',0,
                           [{clause,0,
                                [{var,0,'Msg'}],
                                [],
                                [{call,0,
                                     {remote,0,{atom,0,handle},{atom,0,msg}},
                                     [{var,0,'Msg'}]}]}]}]}]}
    ],

    Wsf = (catch run_forms(Forms, [recv_opt_info])),
    {warnings, [{none,beam_ssa_recv,matches_any_message}]} = Wsf,

    ok.

%% OTP-17260: Test that opportunistic warnings can be disabled.
opportunistic_warnings(Config) ->
    Source = <<"m(_) -> ok;
                m(_) -> error.

                a() -> <<0.5>>.
                b() -> Bin = <<1,2,3,7:4>>, <<Bin/binary>>.
                c() -> Size = bad_size, <<1:Size>>.

                i() -> {a,b,c}, ok.
           ">>,

    %% Don't disable any warnings.
    Ts1 = [{nothing_disabled,
            Source,
            [],
            {warnings,[{{2,17},sys_core_fold,{nomatch,{shadow,1,{m,1}}}},
                       {{4,24},v3_core,{failed,bad_binary}},
                       {{5,45},sys_core_fold,{failed,{embedded_unit,8,28}}},
                       {{6,43},beam_core_to_ssa,{failed,bad_segment_size}},
                       {{8,24},sys_core_fold,{ignored,useless_building}}
                      ]}}],
    [] = run(Config, Ts1),

    %% Disable all opportunistic warnings.
    Ts2 = [{all_disabled,
            Source,
            [nowarn_opportunistic],
            []}],
    [] = run(Config, Ts2),

    %% Disable warnings for patterns that don't match.
    Ts3 = [{nomatch_disabled,
            Source,
            [nowarn_nomatch],
            {warnings,[{{4,24},v3_core,{failed,bad_binary}},
                       {{5,45},sys_core_fold,{failed,{embedded_unit,8,28}}},
                       {{6,43},beam_core_to_ssa,{failed,bad_segment_size}},
                       {{8,24},sys_core_fold,{ignored,useless_building}}
                      ]}}],
    [] = run(Config, Ts3),

    %% Disable warnings for failures.
    Ts4 = [{failures_disabled,
            Source,
            [nowarn_failed],
            {warnings,[{{2,17},sys_core_fold,{nomatch,{shadow,1,{m,1}}}},
                       {{8,24},sys_core_fold,{ignored,useless_building}}
                      ]}}],
    [] = run(Config, Ts4),

    %% Disable warnings for useless building.
    Ts5 = [{disabled_useless_building,
            Source,
            [nowarn_ignored],
            {warnings,[{{2,17},sys_core_fold,{nomatch,{shadow,1,{m,1}}}},
                       {{4,24},v3_core,{failed,bad_binary}},
                       {{5,45},sys_core_fold,{failed,{embedded_unit,8,28}}},
                       {{6,43},beam_core_to_ssa,{failed,bad_segment_size}}
                      ]}}],
    [] = run(Config, Ts5),

    %% Disable warnings for useless building and failures.
    Ts6 = [{disabled_combination,
            Source,
            [nowarn_ignored,nowarn_failed],
            {warnings,[{{2,17},sys_core_fold,{nomatch,{shadow,1,{m,1}}}}
                      ]}}],
    [] = run(Config, Ts6),


    ok.

%% Test value-based error handling (EEP 49).
eep49(Config) ->
    Ts = [{basic,
           <<"foo(X) ->
                  maybe
                      %% There should be no warning.
                      Always ?= X,
                      Always
                  end.
           ">>,
           [{feature,maybe_expr,enable}],
           []},
          {disabled,
           <<"foo() -> maybe.                        %Atom maybe.
           ">>,
           [{feature,maybe_expr,disable}],
           []}
	 ],
    run(Config, Ts),
    ok.

%% GH-6158: There would be a warning for a clause that could not match.
inline_list_funcs(Config) ->
    Ts = [{basic,
           <<"all(L) ->
                  lists:all(fun erlang:is_integer/1, L).
              any(L) ->
                  lists:any(fun erlang:is_integer/1, L).
              foreach(L) ->
                  lists:foreach(fun erlang:is_integer/1, L).
              map(L) ->
                  lists:map(fun erlang:abs/1, L).
              filter(L) ->
                  lists:map(fun erlang:is_integer/1, L).
              foldl(L) ->
                  lists:foldl(fun erlang:is_function/2, L).
              foldr(L) ->
                  lists:foldl(fun erlang:is_function/2, L).
              mapfoldl(L) ->
                  lists:mapfoldl(fun erlang:is_function/2, L).
              mapfoldr(L) ->
                  lists:mapfoldr(fun erlang:is_function/2, L).
              ">>,
           [inline_list_funcs],
           []}
         ],
    run(Config, Ts),

    ok.


%%%
%%% End of test cases.
%%%

run(Config, Tests0) ->
    do_run(Config, Tests0),

    %% Now test without column numbers.
    Tests = [lines_only(T) || T <- Tests0],
    do_run(Config, Tests).

lines_only({Name,Test,Opts,{warnings,Result0}}) ->
    Result1 = lists:map(fun lines_only_1/1, Result0),
    Result = {warnings,lists:usort(Result1)},
    {Name,Test,[{error_location,line}|Opts],Result};
lines_only(NoWarnings) -> NoWarnings.

lines_only_1({File,Es0}) when is_list(Es0) ->
    Es = [lines_only_1(E) || E <- Es0],
    {File,Es};
lines_only_1({Loc,Mod,Error}) ->
    case Loc of
        {Line,_Col} ->
            {Line,Mod,Error};
        Line when is_integer(Line) ->
            {Line,Mod,Error}
    end.

do_run(Config, Tests) ->
    F = fun({N,P,Ws,E}, BadL) ->
                io:format("### ~s\n", [N]),
                case catch run_test(Config, P, Ws) of
                    E -> 
                        BadL;
                    Bad -> 
                        io:format("~nTest ~p failed. Expected~n  ~p~n"
                                  "but got~n  ~p~n", [N, E, Bad]),
			fail()
                end
        end,
    lists:foldl(F, [], Tests).

%% Compiles a test module and returns the list of errors and warnings.

run_test(Conf, Test0, Warnings) ->
    Module = "warnings" ++ test_lib:uniq(),
    Filename = Module ++ ".erl",
    DataDir = ?privdir,
    Test1 = ["-module(", Module, "). -file( \"", Filename, "\", 1). ", Test0],
    Test = iolist_to_binary(Test1),
    File = filename:join(DataDir, Filename),
    Opts = [binary,export_all,return|Warnings],
    ok = file:write_file(File, Test),

    %% Compile once just to print all warnings (and cover more code).
    compile:file(File, [binary,export_all,report|Warnings]),

    %% Test result of compilation.
    Res = get_warnings(compile:file(File, Opts)),
    case Res of
        [] -> [];
        {warnings, Ws} -> print_warnings(Ws, Test)
    end,
    file:delete(File),
    Res.

run_forms(Forms, Warnings) ->
    get_warnings(compile:forms(Forms, [binary,return|Warnings])).

get_warnings(Result) ->
    case Result of
        {ok, _M, Bin, []} when is_binary(Bin) ->
            [];
        {ok, _M, Bin, Ws0} when is_binary(Bin) ->
            %% We are not interested in warnings from
            %% erl_lint here.
            WsL = [{F,[W || {_,Mod,_}=W <- Ws,
                            Mod =/= erl_lint]} ||
                      {F,Ws} <- Ws0],
            case WsL of
                [{_File,Ws}] -> {warnings, Ws};
                _ -> list_to_tuple([warnings, WsL])
            end
    end.

print_warnings(Warnings, Source) ->
    Lines = binary:split(Source, <<"\n">>, [global]),
    Cs = [print_warning(W, Lines) || W <- Warnings],
    io:put_chars(Cs),
    ok.

print_warning({{LineNum,Column},Mod,Data}, Lines) ->
    Line0 = lists:nth(LineNum, Lines),
    <<Line1:(Column-1)/binary,_/binary>> = Line0,
    Spaces = re:replace(Line1, <<"[^\t]">>, <<" ">>, [global]),
    CaretLine = [Spaces,"^"],
    [io_lib:format("~p:~p: ~ts\n",
                   [LineNum,Column,Mod:format_error(Data)]),
     Line0, "\n",
     CaretLine, "\n\n"];
print_warning(_, _) ->
    [].

fail() ->
    ct:fail(failed).
